module ramp3d(r=100, start=30, end=120, thickness=3, width=30, height=50, segments=10){
  delta = (end-start)/segments;
  dz = height/segments;
  e = 1;

  union()
  for (i = [0:segments-1]){
    assign (a = [(r+width/2)*cos(i*delta), (r+width/2)*sin(i*delta), i*dz],
    b = [(r-width/2)*cos(i*delta), (r-width/2)*sin(i*delta), i*dz],
    c = [(r+width/2)*cos((i+e)*delta), (r+width/2)*sin((i+e)*delta), (i+e)*dz],
    d = [(r-width/2)*cos((i+e)*delta), (r-width/2)*sin((i+e)*delta), (i+e)*dz]){
      assign(a2 = a + [0,0,thickness],
             b2 = b + [0,0,thickness],
             c2 = c + [0,0,thickness],
             d2 = d + [0,0,thickness]){
        polyhedron( points=[a,b,c,d,a2,b2,c2,d2], triangles=[ [0,1,2],[1,2,3],[4,5,6],[5,6,7], [0,1,4],[1,4,5], [2,3,6], [3,6,7], [0,2,4],[2,4,6],  [1,3,5],[3,5,7] ]);
      }
    }
  }
}

module ramp25d(r=100, start=30, end=120, thickness=3, width=30, height=50, segments=10){
  delta = (end-start)/segments;
  dz = height/segments;

//  linear_extrude(height=thickness)
  union()
  for (i = [0:segments-1]){
    assign (a = [(r+width/2)*cos(i*delta), (r+width/2)*sin(i*delta), i*dz],
    b = [(r-width/2)*cos(i*delta), (r-width/2)*sin(i*delta), i*dz],
    c = [(r+width/2)*cos((i+1)*delta), (r+width/2)*sin((i+1)*delta), (i+1)*dz],
    d = [(r-width/2)*cos((i+1)*delta), (r-width/2)*sin((i+1)*delta), (i+1)*dz]){
      polyhedron( points=[a,b,c,d], triangles=[ [0,1,2], [1,2,3] ]);
    }
  }
}

module ramp2d(r=100, start=30, end=120, thickness=3, width=30, segments=10){
  delta = (end-start)/segments;
  dz = height/segments;

  linear_extrude(height=thickness)
  union()
  for (i = [0:segments-1]){
    assign (a = [(r+width/2)*cos(i*delta), (r+width/2)*sin(i*delta)],
    b = [(r-width/2)*cos(i*delta), (r-width/2)*sin(i*delta)],
    c = [(r+width/2)*cos((i+1)*delta), (r+width/2)*sin((i+1)*delta)],
    d = [(r-width/2)*cos((i+1)*delta), (r-width/2)*sin((i+1)*delta)]){
      polygon( points=[a,b,d,c] );
    }
  }
}

//function f(x) = 0.5 + 0.5*sin(x*360);
//ramp2d(segments=30, width=120, thickness=20);
//color([f($t), 1-f($t), 0.5])
//ramp3d(segments=300, start=0, end=90 + (720-90)*f($t), height=500-450*f($t), width=300-250*f($t));


function bezier(p1, p2, c, t) = (p1*(1-t) + c*t)*(1-t) + p2*t;
function bezier_normal(p1, p2, c, t) = rotate2d(normalize(bezier(p1, p2, c, t+0.01)-bezier(p1, p2, c, t)),90);

//function cubic_bezier(p1, c1, c2, p2, t) = (1-t)*((1-t)*p1 + t*c1) + t*((1-t)*c2 + t*p2);
function cubic_bezier(p1, c1, c2, p2, t) = pow((1-t),3) * p1 + 3*pow((1-t),2)*t*c1 + 3*(1-t)*pow(t,2)*c2 + pow(t, 3)*p2;
function cubic_bezier_normal(p1, c1, c2, p2, t) = rotate2d(normalize(cubic_bezier(p1, c1, c2, p2, t+0.01)-cubic_bezier(p1, c1, c2, p2, t)),90);

function rotate2d(p, angle) = p * [[cos(angle), -sin(angle)],[sin(angle),cos(angle)]];
function length(v) = sqrt(v[0]*v[0] + v[1]*v[1]);
function normalize(v) = v/length(v);

module double_bezier_strip(p1, p2, c, width=100, $fn=10){
  v1 = width * normalize(rotate2d((c-p1), 90));
  v2 = width * normalize(rotate2d((p2-c), 90));

  union()
  for (t=[0:$fn-1]){
    assign(a=bezier(p1-v1/2, p2-v2/2, c, t/$fn),
           b=bezier(p1+v1/2, p2+v2/2, c, t/$fn),
           c=bezier(p1-v1/2, p2-v2/2, c, (t+1)/$fn),
           d=bezier(p1+v1/2, p2+v2/2, c, (t+1)/$fn)
    ){
      polygon(points = [a,b,d,c]);
    }
  }
}

module bezier_strip(p1, p2, c, width=100, $fn=10){
  union()
  for (t=[0:$fn-1]){
    assign( n1 = width * bezier_normal(p1, p2, c, t/$fn),
            n2 = width * bezier_normal(p1, p2, c, (t+1)/$fn)
    ){
      assign(a=bezier(p1, p2, c, t/$fn) - n1/2,
             b=bezier(p1, p2, c, t/$fn) + n1/2,
             c=bezier(p1, p2, c, (t+1)/$fn) - n2/2,
             d=bezier(p1, p2, c, (t+1)/$fn) + n2/2
      ){
        polygon(points = [a,b,d,c]);
      }
    }
  }
}

module cubic_bezier_strip(p1, c1, c2, p2, width=30, $fn=10){
  union()
  for (t=[0:$fn-1]){
    assign( n1 = width * cubic_bezier_normal(p1, c1, c2, p2, t/$fn),
            n2 = width * cubic_bezier_normal(p1, c1, c2, p2, (t+1)/$fn)
    ){
      assign(a=cubic_bezier(p1, c1, c2, p2, t/$fn) - n1/2,
             b=cubic_bezier(p1, c1, c2, p2, t/$fn) + n1/2,
             c=cubic_bezier(p1, c1, c2, p2, (t+1)/$fn) - n2/2,
             d=cubic_bezier(p1, c1, c2, p2, (t+1)/$fn) + n2/2
      ){
        polygon(points = [a,b,d,c]);
      }
    }
  }
}

//examples;
//p1=[0,0];
//p2=[400,0];
//time=$t;
//c=[1000*cos(360*time), 1000*sin(360*time)];
//bezier_strip(p1,p2,c, width=30, $fn=50);
//double_bezier_strip(p1,p2,c);
//cubic_bezier_strip([0,0], [30,200], [70,-100], [100,0], width=3, $fn=50);

module cross(p, color, rot=false){
  color(color)
  translate(p)
  if (rot){
    rotate([0,0,45]){
      square([2,10],center=true);
      square([10,2],center=true);
    }
  } else {
    square([2,10],center=true);
    square([10,2],center=true);
  }
}

//partyland SkyRide ramp:

//left curve (extracted from SVG)
left_curves = [
[ [866.3125, 4126.26221] , [863.52855, 4125.16962] , [860.77212, 4124.2006599999995] , [858.1875, 4123.13721] ],
[ [858.1875, 4123.13721] , [795.2397, 4096.47712] , [737.62697, 4062.32458] , [686.08937, 4024.6203] ],
[ [686.08937, 4024.6203] , [634.34256, 3986.6060199999997] , [589.94615, 3945.80051] , [558.09375, 3909.35596] ],
[ [558.09375, 3909.35596] , [508.70198, 3857.32226] , [462.07696, 3800.14576] , [423.82333, 3738.9331199999997] ],
[ [423.82333, 3738.9331199999997] , [371.45364, 3652.08735] , [337.23176, 3562.0545199999997] , [328.80031, 3468.23117] ],
[ [328.80031, 3468.23117] , [324.87728, 3416.98409] , [327.405, 3359.2000399999997] , [337.34375, 3312.94971] ],
[ [337.34375, 3312.94971] , [352.07296, 3258.00101] , [365.58763, 3208.4415099999997] , [379.55911, 3157.9293099999995] ],
[ [379.55911, 3157.9293099999995] , [393.04306, 3109.18901] , [407.53055, 3057.4761099999996] , [420.02822, 3012.89171] ],
[ [420.02822, 3012.89171] , [428.99983, 2980.88691] , [438.01459, 2948.73791] , [447.1875, 2915.85601] ]
];

//right curve (extracted from SVG)
right_curves = [[[903.40625, 3981.19971] , [901.49126, 3981.12745] , [899.47043, 3980.98648] , [897.4375, 3980.63721] ],
[ [897.4375, 3980.63721] , [850.07364, 3971.76653] , [802.13522, 3935.5145199999997] , [762.97534, 3904.81508] ],
[ [762.97534, 3904.81508] , [728.64628, 3876.26548] , [698.67354, 3845.25839] , [666.15739, 3808.05912] ],
[ [666.15739, 3808.05912] , [627.11894, 3760.93581] , [593.23271, 3704.1217699999997] , [568.63699, 3642.17094] ],
[ [568.63699, 3642.17094] , [547.69736, 3587.65574] , [534.95178, 3527.1106099999997] , [531.50017, 3464.1354899999997] ],
[ [531.50017, 3464.1354899999997] , [529.92177, 3432.42857] , [530.58078, 3399.41676] , [533.375, 3364.66846] ],
[ [533.375, 3364.66846] , [539.6295, 3319.73286] , [549.03152, 3270.12142] , [560.69624, 3219.3152099999998] ],
[ [560.69624, 3219.3152099999998] , [572.36096, 3168.5090099999998] , [586.2884, 3116.5080099999996] , [601.59375, 3066.7934099999998] ],
[ [601.59375, 3066.7934099999998] , [610.36787, 3038.30371] , [619.83234, 3007.99671] , [630.25, 2977.5434099999998]]];

%for (curve = left_curves){
  assign(p1=curve[0], c1=curve[1], c2=curve[2], p2=curve[3]){
    cubic_bezier_strip(p1, c1, c2, p2, width=10, $fn=50);
  }
}

%for (curve = right_curves){
  assign(p1=curve[0], c1=curve[1], c2=curve[2], p2=curve[3]){
    cubic_bezier_strip(p1, c1, c2, p2, width=10, $fn=50);
  }
}

function cubic_bezier_z(p1, c1, c2, p2, t, z) = [cubic_bezier(p1, c1, c2, p2, t)[0], cubic_bezier(p1, c1, c2, p2, t)[1], z];

module cubic_bezier_pair(C1, C2, t1, t2, $fn=10){
  union()
  for (t = [0:$fn-1]){
    assign(
      a = cubic_bezier_z(C1[0], C1[1], C1[2], C1[3], t/$fn, ramp_height(t1 + (t2-t1)*t/$fn)),
      b = cubic_bezier_z(C2[0], C2[1], C2[2], C2[3], t/$fn, ramp_height(t1 + (t2-t1)*t/$fn)),
      c = cubic_bezier_z(C1[0], C1[1], C1[2], C1[3], (t+1)/$fn, ramp_height(t1 + (t2-t1)*(t+1)/$fn)),
      d = cubic_bezier_z(C2[0], C2[1], C2[2], C2[3], (t+1)/$fn, ramp_height(t1 + (t2-t1)*(t+1)/$fn))
    ){
      polyhedron (points=[a,b,c,d], triangles=[[0,1,2],[1,2,3]]);
    }
  }
}

module cubic_bezier_ramp(C1, C2, t1, t2, thickness=10, $fn=10, max_height){
  union()
  for (t = [0:$fn-1]){
    assign(
      a = cubic_bezier_z(C1[0], C1[1], C1[2], C1[3], t/$fn, ramp_height(t1 + (t2-t1)*t/$fn, max_height=max_height)),
      b = cubic_bezier_z(C2[0], C2[1], C2[2], C2[3], t/$fn, ramp_height(t1 + (t2-t1)*t/$fn, max_height=max_height)),
      c = cubic_bezier_z(C1[0], C1[1], C1[2], C1[3], (t+1)/$fn, ramp_height(t1 + (t2-t1)*(t+1)/$fn, max_height=max_height)),
      d = cubic_bezier_z(C2[0], C2[1], C2[2], C2[3], (t+1)/$fn, ramp_height(t1 + (t2-t1)*(t+1)/$fn, max_height=max_height))
    ){
      assign(a2 = a + [0,0,thickness], b2 = b + [0,0,thickness],
             c2 = c + [0,0,thickness], d2 = d + [0,0,thickness]){
        polyhedron (points=[a,b,c,d, a2,b2,c2,d2], triangles=[[0,1,2],[1,2,3],[4,5,6],[5,6,7], [0,2,4], [2,4,6], [1,3,5],[3,5,7]]);
      }
    }
  }
}

adjustment=20;
function ramp_height(t, max_height) = (max_height/0.28222)*(1+cos(-adjustment+t*(180+adjustment)));

module skyride_ramp(max_height=26){
  num_segs=9;
  scale(0.28222)
  for (N=[0:num_segs-1]){
    assign(C1=right_curves[N], C2=left_curves[N]){
      cubic_bezier_ramp(C1, C2, N/num_segs, (N+1)/num_segs, $fn=30, max_height=max_height);
    }
  }
}
